﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace PlanetViewer
{
    public class Terrain
    {
        public Planet _Planet;

        private int _Length;
        public int Length { get { return this._Length; } }
        public float SpineAngle { get { return 0f; } }

        private Heightmap _HMap;
        public Heightmap Heightmap { get { return this._HMap; } }
        public int Seed { get { if (this._HMap != null) return this._HMap.Seed; else return 0; } }

        public TerrainData Data;

        public VertexPositionColor[] UGVertices;
        public int[] UGIndices;

        private int _radius { get { return this._Planet.Radius; } }
        private int _depth { get { return this._Planet.Depth; } }

        public float _Height;
        private readonly float _defaultTerrainHeight = 0.3f;
        private readonly float _terrainMargin = 0.9f;

        private Color _boundaryColor;
        public Color BoundaryColor
        {
            get { return this._boundaryColor; }
            set { this._boundaryColor = value; }
        }

        private Color _boundaryDColor;
        public Color UndergroundColor
        {
            get { return this._boundaryDColor; }
            set { this._boundaryDColor = value; }
        }

        private Color _terrainColor;
        public Color TerrainColor
        {
            get { return this._terrainColor; }
            set { this._terrainColor = value; }
        }

        public TerrainDrawMask DrawMask;

        public Terrain(Planet planet, int length)
        {
            this._Planet = planet;

            this._Length = length;

            this.Data = new TerrainData(this, length);

            this.UndergroundColor = new Color(45, 30, 15);
            this.BoundaryColor = new Color(75, 50, 30);
            this.TerrainColor = Color.SandyBrown;
        }

        public void SetupHeightmap(int seed = 0, float height = 0f)
        {
            if (height == 0f) height = _depth * _defaultTerrainHeight;
            this._Height = height;

            VertexPositionColor V = new VertexPositionColor();
            V.Position.X = _radius;
            V.Color = this.BoundaryColor;

            this.Data.BoundaryVertex = V;

            V.Position.X = _radius - _depth;
            V.Color = this.UndergroundColor;

            this.Data.UndergroundVertex = V;

            this._HMap = new Heightmap(this, this._Length, seed);

            for (int i = 0; i < this._Length; i++)
            {
                float h = this._HMap.GetData(i);

                V = new VertexPositionColor();
                V.Position.X = _radius + height * (1 + _terrainMargin * h);
                V.Color = this.TerrainColor;

                this.Data.SetTerrainVertex(i, V);
            }

            this.CreateUndergroundMesh();
        }

        public void CreateUndergroundMesh()
        {
            this.UGVertices = new VertexPositionColor[this._Length];
            int nTris = this._Length - 2;
            this.UGIndices = new int[nTris * 3];

            VertexPositionColor B = this.Data.BoundaryVertex;

            for (int a = 0; a < this._Planet.nSpines; a++)
            {
                VertexPositionColor V = util.RotateVertex(B, a * this._Planet.SpineAngle);
                this.UGVertices[a] = V;
            }

            for (int a = 1; a < nTris + 1; a++)
            {
                int i = 3 * (a - 1);

                this.UGIndices[i] = 0;
                this.UGIndices[i + 1] = a;
                this.UGIndices[i + 2] = a + 1;
            }
        }

        #region Terrain Editing

        #region Vertices

        public void ChangeVertexHeight(int spine, float height) { this.Heightmap.SetSpineHeight(spine, height); }

        public void AddVertexHeight(int spine, float dheight) { ChangeVertexHeight(spine, this.Heightmap.GetData(spine) + dheight); }

        public void RefreshVertexHeight(int spine)
        {
            VertexPositionColor V = this.Data.GetTerrainVertex(spine);

            V.Position.X = this._radius + this._Height * (1 + _terrainMargin * this.Heightmap.GetData(spine));

            this.Data.SetTerrainVertex(spine, V);
        }

        #endregion

        #region Edges

        public void ChangeEdgeHeight(int spine, float height1, float height2)
        {
            this.Heightmap.SetSpineHeight(spine, height1);
            this.Heightmap.SetSpineHeight(spine + 1, height2);
        }

        public void AddEdgeHeight(int spine, float dheight1, float dheight2 = 0f)
        {
            if (dheight2 == 0f) dheight2 = dheight1;

            ChangeEdgeHeight(spine, this.Heightmap.GetData(spine) + dheight1, this.Heightmap.GetData(spine + 1) + dheight2);
        }

        #endregion

        #endregion

        public float GetTerrainRadius(int spine)
        {
            return this.Data.GetTerrainVertex(spine).Position.X;
        }

        public float GetTerrainHeight(int spine)
        {
            return GetTerrainRadius(spine) - this._radius;
        }
    }

    public class TerrainData
    {
        private Terrain _Terrain;

        public int Length { get { return this._Terrain.Length; } }

        private VertexPositionColor _BoundaryD;
        private VertexPositionColor _BoundaryR;
        private VertexPositionColor[] _Surface;

        public TerrainData(Terrain terrain, int length)
        {
            this._Terrain = terrain;

            this._BoundaryD = new VertexPositionColor();
            this._BoundaryR = new VertexPositionColor();

            this._Surface = new VertexPositionColor[length];
            for (int i = 0; i < length; i++)
            {
                this._Surface[i] = new VertexPositionColor();
            }
        }

        private int _Wrap(int index)
        {
            return util.Modulo(index, this.Length);
        }
        
        /// <summary>
        /// Returns VertexPositionColor 
        /// </summary>
        /// <param name="spine"></param>
        /// <returns></returns>
        public VertexPositionColor GetTerrainVertex(int spine)
        {
            return this._Surface[_Wrap(spine)];
        }

        public void SetTerrainVertex(int spine, VertexPositionColor V)
        {
            this._Surface[_Wrap(spine)] = V;
        }

        public Edge GetTerrainEdge(int spine)
        {
            return new Edge(this._Surface[_Wrap(spine)], this._Surface[_Wrap(spine + 1)]);
        }

        public void SetTerrainEdge(int spine, Edge E)
        {
            this._Surface[_Wrap(spine)] = E.V1;
            this._Surface[_Wrap(spine+1)] = E.V2;
        }

        public VertexPositionColor BoundaryVertex
        {
            get { return this._BoundaryR; }
            set { this._BoundaryR = value; }
        }

        public VertexPositionColor UndergroundVertex
        {
            get { return this._BoundaryD; }
            set { this._BoundaryD = value; }
        }

        /*public Face GetTerrainFace(int spine)
        {
            return new Face(this._Surface[_Wrap(spine)], this._Surface[_Wrap(spine + 1)], this._Boundary, this._Boundary);
        }*/
    }

    public struct Edge
    {
        public VertexPositionColor[] E;

        public VertexPositionColor V1 { get { return E[0]; } }
        public VertexPositionColor V2 { get { return E[1]; } }

        public Edge(VertexPositionColor v1, VertexPositionColor v2)
        {
            E = new VertexPositionColor[2];
            E[0] = v1; E[1] = v2;
        }
    }
}
